/*==============================================================================
  File Name   : main.cpp

  Description : Entry point for TCP server developed by using MinGW.

  Created on  : 2008-10-14
  Created by  : Shiou Ming, Lee

  Updated on  : 2009-02-08
  Updated by  : Shiou Ming, Lee
  Changes     : version 0.1.4.5
                - Fixed a bug for not checking return value of accept(), which
                  may cause memory leak if accept() fails.
                - Added checking for return value of recv() and send().
                - Display the detail of various socket errors on screen.
                version 0.1.3.4
                - Added support for user to specify server connection info via
                  command line: TCPServer [<listening IP> <listening port>]
                - Cleaned up some codes, and added extra info display on screen.
                version 0.1.2.3
                - Use protocol family PF_INET while creating socket, instead of 
                  pass in address family AF_INET as first parameter of socket().
                - Cleaned up 2 lines of unused code.
                - Appended connection ID into the data to be returned to client.
                version 0.1.1.2
                - Added some simple functionalities of a multithreaded TCP 
                  server: handle client connection, receive data, send data.
                version 0.1.0.1
                - Branched from "basic_app" template and redefine as TCP server.
                
  Cautions    : (important notes)
  ============================================================================*/


// Targeted for Windows XP / Server 2003 or above
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501


//------------------------------------------------------------------------------
//-- INCLUDES & DEFINES
//------------------------------------------------------------------------------
#include <stdio.h>
#include <string.h>
#include <tchar.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iptypes.h>
#include <process.h>


#define _APP_VERSION_  _T("0.1.2.3")
#define _APP_NAME_     _T("TCP Server Demo")

#define _SCK_VERSION1_ 0x0101
#define _SCK_VERSION2_ 0x0202

#ifdef _UNICODE
  #define _tperror    _wperror
  #define _tcserror_s _wcserror_s
#else
  #define _tperror    perror
  #define _tcserror_s strerror_s
#endif


//------------------------------------------------------------------------------
//-- TYPEDEF / STRUCT / UNION / ENUM
//------------------------------------------------------------------------------
struct ST_CMDLINE_OPTIONS
{
  bool bOptState_Help;
  bool bOptState_Version;
  bool bOptState_DebugMode;
};

struct ST_CLIENT_SOCKET
{
  SOCKET objSocket;
  UINT uConnectionID;
  sockaddr_in clientAddress;
};

struct ST_THREAD_LIST_NODE
{
  HANDLE hThread;
  UINT uThreadID;
  ST_THREAD_LIST_NODE *pNextThreadNode;
};


//------------------------------------------------------------------------------
//-- GLOBAL
//------------------------------------------------------------------------------
ST_CMDLINE_OPTIONS g_stCmdLineOptions = {false, false, false};


//------------------------------------------------------------------------------
//-- FUNCTION PROTOTYPES
//------------------------------------------------------------------------------
bool HandleCmdLineOpt(int _argc, _TCHAR *_argv[]);
bool MatchCmdLineOpt(const _TCHAR *_szKeywordList[],
                     int _nKeywordCount, 
                     const _TCHAR *_szCmdLineArg);
bool ListenForConnection(const _TCHAR *_szIPAddress, unsigned short _nPortNum);
unsigned __stdcall AnswerIncomingConnection(void *a);
void PrintLastSockError(const _TCHAR *_szAppErrMsg);

#ifdef _WIN32
// Some wrappers for certain Linux/Unix-only functions
// The wrappers for inet_ntop() and inet_pton() are found from:
//   http://www.mail-archive.com/users@ipv6.org/msg02107.html
const _TCHAR * inet_ntop(int af, const void *src, _TCHAR *dst, socklen_t cnt);
int inet_pton(int af, const _TCHAR *src, void *dst);
#endif


//------------------------------------------------------------------------------
//-- MAIN FUNCTION
//------------------------------------------------------------------------------
int _tmain(int _argc, _TCHAR *_argv[], _TCHAR *_envp[])
{
  //-- Try to process parameters in separate function, don't mess up main()
  // Note: As a sample for demostrating simple TCP server, the implementation
  //       of HandleCmdLineOpt() function can be ignored.
  if (!HandleCmdLineOpt(_argc, _argv)) return 0;

  //-- Main logic
  if (_argc == 3)
  {
    // Assumed that 2 parameter inputs will be: listening IP, listening port
    ListenForConnection(_argv[1], _ttoi(_argv[2]));
  }
  else
  {
    // Listen to port 9999, of ANY address (specified in ListenForConnection())
    ListenForConnection(_T(""), 9999);
  }

  //-- Stop the console screen before quit for displaying the output.
  _tprintf(_T("\n\nPress <ENTER> key to exit"));
  int quitFlag = _fgettchar();

  return 0;
}


//------------------------------------------------------------------------------
//-- FUNCTION IMPLEMENTATIONS
//------------------------------------------------------------------------------
// Return value: true if main() should continue the following logic; false
//               otherwise.
bool HandleCmdLineOpt(int _argc, _TCHAR *_argv[])
{
  bool bContinueMainLogic = true;
  
  // Declare the list of valid keywords for various command line options
  const int KWORDCOUNT_VERSION = 3;
  const _TCHAR *ARG_KEYWORD_VERSION[KWORDCOUNT_VERSION]
      = {_T("--version"), _T("-v"), _T("-ver")};

  const int KWORDCOUNT_HELP = 3;      
  const _TCHAR *ARG_KEYWORD_HELP[KWORDCOUNT_HELP]
      = {_T("--help"), _T("-h"), _T("?")};

  const int KWORDCOUNT_DEBUG = 2;      
  const _TCHAR *ARG_KEYWORD_DEBUG[KWORDCOUNT_DEBUG]
      = {_T("--debug"), _T("-d")};

  // Loop through command line options and take necessary actions
  // _tprintf(_T("\n=== Command Line Parameters ===\n"));
  for (int i = 0; i < _argc; i++)
  {
    if ((!g_stCmdLineOptions.bOptState_Version) && 
        (MatchCmdLineOpt(ARG_KEYWORD_VERSION, KWORDCOUNT_VERSION, _argv[i])))
    {
      g_stCmdLineOptions.bOptState_Version = true;
      _tprintf(_T("\nVERSION INFO\n------------"));
      _tprintf(_T("\n%s version %s\n"), _APP_NAME_, _APP_VERSION_);
      bContinueMainLogic = false;
      break;
    }
    else if ((!g_stCmdLineOptions.bOptState_Help) && 
             (MatchCmdLineOpt(ARG_KEYWORD_HELP, KWORDCOUNT_HELP, _argv[i])))
    {
      g_stCmdLineOptions.bOptState_Help = true;
      _tprintf(_T("\nHELP INFO\n---------"));
      _tprintf(_T("\nUSAGE: TCPServer [<listening IP> <listening port>]\n"));
      bContinueMainLogic = false;
      break;
    }
    else if ((!g_stCmdLineOptions.bOptState_DebugMode) && 
             (MatchCmdLineOpt(ARG_KEYWORD_DEBUG, KWORDCOUNT_DEBUG, _argv[i])))
    {
      g_stCmdLineOptions.bOptState_DebugMode = true;
      _tprintf(_T("\nDEBUG MODE\n----------"));
      _tprintf(_T("\nRunning in debug mode!\n"));
    }
    else
    {
      // _tprintf(_T("\nParameter[%d]\t- %s\n"), i, _argv[i]);
    }
  }
  // _tprintf(_T("\n=== END OF PARAMETERS ===\n\n"));

  return bContinueMainLogic;
}


// _szKeywordList: List of supported keyword for particular command line option
// _nKeywordCount: Number of supported keyword in the _szKeywordList list
// _szCmdLineArg : Actual argument parsed from command line
bool MatchCmdLineOpt(const _TCHAR *_szKeywordList[],
                     int _nKeywordCount, 
                     const _TCHAR *_szCmdLineArg)
{
  // Take one command line option and compare with the list of valid keywords,
  // return true if found matching.
  for (int i = 0; i < _nKeywordCount; i++)
  {
    if (0 == _tcscmp(_szKeywordList[i], _szCmdLineArg)) return true;
  }

  return false;
}


// _szIPAddress: IP address to be listening from, in x.x.x.x format
// _nPortNum   : Listen incoming connection from this port.
bool ListenForConnection(const _TCHAR *_szIPAddress, unsigned short _nPortNum)
{
  UINT uActiveConnectionCount;
  UINT uConnectionID;

  SOCKET serverSocket;
  WSADATA wsaInfo;

  // Step 1: Init WinSock (for WinSock only, not necessary for Unix/Linux)
  int nError = WSAStartup(_SCK_VERSION2_, &wsaInfo);
  if (nError)
  {
    _tprintf(_T("\nError: WSAStartup() failed [%d]"), nError);
    return false;  // Check if WinSock init successful
  }

  // Verify if WinSock version matches with desired one (WinSock only)
  if (wsaInfo.wVersion != _SCK_VERSION2_)
  {
    _tprintf(_T("\nError: WinSock version unmatch."));
    _tprintf(_T("\nAction: Clean up then exit.\n"));
    WSACleanup();
    return false;
  }

  // Printing extra info :)
  _TCHAR szHostName[MAX_HOSTNAME_LEN] = {0};
  if (SOCKET_ERROR == gethostname(szHostName, MAX_HOSTNAME_LEN))
    PrintLastSockError(_T("\nError gethostname()"));
  else
    _tprintf(_T("Local hostname \t[%s]\n"), szHostName);

  // Define socket address to be use for socket creation
  // AF_INET family has address format that consists of host and port
  sockaddr_in serverAddress;
  memset(&serverAddress, 0, sizeof(serverAddress));
  serverAddress.sin_family = AF_INET;
  serverAddress.sin_port = htons(_nPortNum);
  _tprintf(_T("Listen to port \t[%d]\n"), _nPortNum);

  if (_tcslen(_szIPAddress) >= 7)
  {
    // Param for inet_addr() is in x.x.x.x format
    _tprintf(_T("Listen to IP \t[%s]\n"), _szIPAddress);
    serverAddress.sin_addr.s_addr = inet_addr(_szIPAddress);
  }
  else
  {
    _tprintf(_T("IP address param [%s], listen to ANY address instead\n"), 
             _szIPAddress);
    serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
  }

  // Step 2: Create the socket with socket() function
  //   Param 2: SOCK_STREAM = TCP; SOCK_DGRAM = UDP; ...
  //   Param 3: IPPROTO_IP (0); IPPROTO_TCP (6); IPPROTO_UDP(17); ...
  // Normally for TCP and UDP, param 3 (protocol) can be 0 (IPPROTO_IP) as well
  serverSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (INVALID_SOCKET == serverSocket)
  {
    PrintLastSockError(_T("\nError socket()"));
    _tprintf(_T("\nAction: Clean up then exit.\n"));
    WSACleanup();
    return false;
  }

  // Step 3: Bind the socket to the address with bind() function
  // - The socket by default is in blocking mode, can be set to non-blocking
  //   mode with ioctlsocket() function BEFORE binding. Example:
  //     u_long ulArg = 1;
  //     ioctlsocket(serverSocket, FIONBIO, &ulArg);
  if (SOCKET_ERROR == 
        bind(serverSocket, (LPSOCKADDR)&serverAddress, sizeof(sockaddr_in)))
  {
    PrintLastSockError(_T("\nError bind()"));
    _tprintf(_T("\nAction: Close socket and clean up, then exit.\n"));
    closesocket(serverSocket);
    WSACleanup();
    return false;
  }

  // Step 4: Start listen to connection with listen() function, will return 
  //         only if connection request is received.
  //   Param 2: Max connection requests in queue.
  // UNIX kenels typically limit max connection requests in queue to 5.
  _tprintf(_T("Start listen for connection\n"));
  if (SOCKET_ERROR == listen(serverSocket, 5))
  {
    PrintLastSockError(_T("\nError listen()"));
    _tprintf(_T("\nAction: Close socket and clean up, then exit.\n"));
    closesocket(serverSocket);
    WSACleanup();
    return false;
  }

  // Step 5: Waiting for incoming connection with accept() function
  // - The socket by default is in blocking mode.
  // - accept() returns SOCKET for that connection, will be pass to separate 
  //   thread in typical implementation of multi-threaded server.
  uActiveConnectionCount = 0;
  uConnectionID = 1;
  ST_THREAD_LIST_NODE *pThreadListHead = NULL;
  ST_THREAD_LIST_NODE *pThreadListTail = NULL;
  ST_CLIENT_SOCKET *pClientSocket      = NULL;
  while (uConnectionID <= 4)  // simply put limit 4, can be more appropriate one
  {
    _tprintf(_T("Waiting for incoming connection request ...\n"));
    pClientSocket = new ST_CLIENT_SOCKET();
    pClientSocket->objSocket = 
        accept(serverSocket, (sockaddr *)&(pClientSocket->clientAddress), NULL);
    if (INVALID_SOCKET == pClientSocket->objSocket)
    {
      PrintLastSockError(_T("\nError accept()"));
      delete pClientSocket;
      continue;
    }
    pClientSocket->uConnectionID = uConnectionID++;

    unsigned int addr_len = sizeof(pClientSocket->clientAddress);
    if (SOCKET_ERROR==getpeername(pClientSocket->objSocket, 
                                  (sockaddr *) &(pClientSocket->clientAddress), 
                                  (socklen_t *) &addr_len))
    {
      PrintLastSockError(_T("\nError getpeername()"));
    }

    if (pThreadListTail != NULL)
    {
      pThreadListTail->pNextThreadNode = new ST_THREAD_LIST_NODE();
      pThreadListTail = pThreadListTail->pNextThreadNode;
    }
    else
    {
      // Init the linked-list of thread handles for 1st time
      pThreadListHead = new ST_THREAD_LIST_NODE();
      pThreadListTail = pThreadListHead;
    }

    pThreadListTail->pNextThreadNode = NULL;
    pThreadListTail->hThread = 
        (HANDLE)_beginthreadex(0, 0, 
                               AnswerIncomingConnection, 
                               (void *) pClientSocket, 
                               0, &(pThreadListTail->uThreadID));
  }
  _tprintf(_T("Connection ID hit max value.\n"));
  _tprintf(_T("Waiting for all threads before exit.\n"));

  ST_THREAD_LIST_NODE *pThreadListTmp = NULL;  // currently processing node
  while (true)
  {
    pThreadListTmp = pThreadListHead;
    if (pThreadListHead == NULL) break;

    UINT uThreadID = pThreadListHead->uThreadID;
    _tprintf(_T("[%u] Waiting for thread...\n"), uThreadID);

    DWORD dwWaitResult = WaitForSingleObject(pThreadListHead->hThread, 10000);
    if (WAIT_OBJECT_0 == dwWaitResult)
      _tprintf(_T("[%u] Signaled.\n"), uThreadID);
    else if (WAIT_TIMEOUT == dwWaitResult)
      _tprintf(_T("[%u] Timeout.\n"), uThreadID);
    else
      _tprintf(_T("[%u] Error: this is a mutex.\n"), uThreadID);

    pThreadListHead = pThreadListHead->pNextThreadNode;

	   // cleaning
    if (0 == CloseHandle(pThreadListTmp->hThread))
    {
      _tprintf(_T("[%u] Error: CloseHandle() failed [%d].\n"),
               uThreadID, GetLastError());
    }
    delete pThreadListTmp;
  }

  WSACleanup();
  return true;
} 


unsigned __stdcall AnswerIncomingConnection(void *param)
{
  ST_CLIENT_SOCKET *pClientSocket = (ST_CLIENT_SOCKET *) param;
  _TCHAR szClientIP[INET_ADDRSTRLEN] = {0};
  unsigned short uClientPort = 0;

  UINT uConnectionID;
  UINT uSleepTimer;
  UINT recvBufferLen;
  UINT sentBufferLen;
  const UINT BUFFER_SIZE = 32;
  _TCHAR szBuffer[BUFFER_SIZE] = {0};

  uConnectionID = pClientSocket->uConnectionID;
  _tprintf(_T("[CONN %u] New client connection thread created.\n"),
           uConnectionID);

  _tcsncpy(szClientIP,
           inet_ntoa(pClientSocket->clientAddress.sin_addr),
           INET_ADDRSTRLEN - 1);
  uClientPort = ntohs(pClientSocket->clientAddress.sin_port);

  _tprintf(_T("[CONN %u] Client address \t[%s:%u]\n"),
           uConnectionID, szClientIP, uClientPort);

  // Step 6: Optionally receive data (if any) sent by client with recv().
  _tprintf(_T("[CONN %u] Receiving data ...\n"), uConnectionID);
  recvBufferLen = recv(pClientSocket->objSocket, szBuffer, BUFFER_SIZE - 6, 0);
  if (SOCKET_ERROR == recvBufferLen)
    PrintLastSockError(_T("\nError socket recv()"));
  else
    _tprintf(_T("[CONN %u] Received data \t\t[%s]\n"), uConnectionID, szBuffer);

  // Step 7: Optionally send data to the client with send().
  // WARNING: Due to the code 'BUFFER_SIZE - 6', connection ID must be < 100.
  _sntprintf(szBuffer, BUFFER_SIZE - 1, _T("%s [%d]"), szBuffer, uConnectionID);
  _tprintf(_T("[CONN %u] Sending data ... \t[%s]\n"), uConnectionID, szBuffer);
  sentBufferLen = send(pClientSocket->objSocket, szBuffer, BUFFER_SIZE, 0);
  if (SOCKET_ERROR == sentBufferLen)
    PrintLastSockError(_T("\nError socket send()"));
  else
    _tprintf(_T("[CONN %u] Data sent.\n"), uConnectionID);
    
  uSleepTimer = uConnectionID * 5 * 1000;
  _tprintf(_T("[CONN %u] Thread will sleep for %u miliseconds then exit.\n"),
           uConnectionID,
           uSleepTimer);
  Sleep(uSleepTimer);

  // Before finishing the business, remember to close the socket.
  closesocket(pClientSocket->objSocket);
  delete pClientSocket;
  pClientSocket = NULL;

  _tprintf(_T("[CONN %u] Thread exited!\n"), uConnectionID);
  return 0;
}


void PrintLastSockError(const _TCHAR *_szAppErrMsg)
{
#ifdef _WIN32
  int nLastSockErrCode = WSAGetLastError();
  _tprintf(_T("\n%s: [%d] %s\n"),
           _szAppErrMsg, nLastSockErrCode, gai_strerror(nLastSockErrCode));
#else
  _tperror(_szAppErrMsg);
#endif
}


/*
// Some WIN32 wrappers for the missing inet_ntop() and inet_pton() support,
// base on resources from Internet.

#ifdef _WIN32

const _TCHAR * inet_ntop(int af, const void *src, _TCHAR *dst, socklen_t cnt)
{
  if (af == AF_INET)
  {
    struct sockaddr_in in;
    memset(&in, 0, sizeof(in));
    in.sin_family = AF_INET;
    memcpy(&in.sin_addr, src, sizeof(struct in_addr));
    getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in), 
                dst, cnt, NULL, 0, NI_NUMERICHOST);

    return dst;
  }
  else if (af == AF_INET6)
  {
    struct sockaddr_in6 in;
    memset(&in, 0, sizeof(in));
    in.sin6_family = AF_INET6;
    memcpy(&in.sin6_addr, src, sizeof(struct in_addr6));
    getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in6), 
                dst, cnt, NULL, 0, NI_NUMERICHOST);

    return dst;
  }

  return NULL;
}


int inet_pton(int af, const _TCHAR *src, void *dst)
{
  struct addrinfo hints, *res, *ressave;
    
  memset(&hints, 0, sizeof(struct addrinfo));
  hints.ai_family = af;
    
  if (getaddrinfo(src, NULL, &hints, &res) != 0)
  {
    _tprintf(_T("Error: Couldn't resolve host %s\n"), src);
    return -1;
  }

  ressave = res;
  while (res)
  {
    memcpy(dst, res->ai_addr, res->ai_addrlen);
    res = res->ai_next;
  }

  freeaddrinfo(ressave);
  return 0;
}

#endif
*/
